/*
 * MIT License
 *
 * Copyright (c) 2019 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.commons.core;

import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

import cn.seaboot.commons.digest.Base64;

/**
 * 设备号
 *
 * @author Mr.css 2018年7月13日 上午11:20:38
 */
public class UUIDUtils {
    private UUIDUtils() {
    }

    private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger();

    /**
     * 生成 12 位唯一识别码
     *
     * 通过时间戳生成的，超高并发下会出现重复。
     *
     * 有时候，明确不会出现高并发，使用 UUID 显得有些浪费性能。
     *
     * @return String
     */
    public static long randomUID() {
        return (System.currentTimeMillis() << 4) ^ (ATOMIC_INTEGER.getAndAdd(1) & 0xf);
    }

    /**
     * 生成 12 位唯一识别码
     *
     * 通过时间戳生成的，超高并发下会出现重复。
     *
     * 有时候，明确不会出现高并发，使用 UUID 显得有些浪费性能。
     *
     * @return String
     */
    public static String genUID() {
        return Long.toHexString(randomUID());
    }

    /**
     * 生成UUID
     *
     * @return uuid
     */
    public static String genUUID() {
        return UUID.randomUUID().toString();
    }

    /**
     * 生成 32 位 UUID
     *
     * <p>
     * Returns a {@code String} object representing this {@code UUID}.
     *
     * <p> The UUID string representation is as described by this BNF:
     * <blockquote><pre>
     * {@code
     * UUID                   = <time_low><time_mid>
     *                          <time_high_and_version>
     *                          <variant_and_sequence>
     * time_low               = 4*<hexOctet>
     * time_mid               = 2*<hexOctet>
     * time_high_and_version  = 2*<hexOctet>
     * variant_and_sequence   = 2*<hexOctet>
     * node                   = 6*<hexOctet>
     * hexOctet               = <hexDigit><hexDigit>
     * hexDigit               =
     *       "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
     *       | "a" | "b" | "c" | "d" | "e" | "f"
     *       | "A" | "B" | "C" | "D" | "E" | "F"
     * }</pre></blockquote>
     *
     * @return A string representation of this {@code UUID}
     */
    public static String genUUID32() {
        UUID uuid = UUID.randomUUID();
        long mostSigBits = uuid.getMostSignificantBits();
        long leastSigBits = uuid.getLeastSignificantBits();
        return (digits(mostSigBits >> 32) +
                digits(mostSigBits) +
                digits(leastSigBits >> 32) +
                digits(leastSigBits));
    }

    /**
     * 二进制 1 后面跟 32 个 0
     */
    private static final long HI = 1L << 32;
    /**
     * 二进制 32 个 1
     */
    private static final long BINARY_32 = (1L << 32) - 1;

    /**
     * Returns val represented by the specified 32 of hex digits.
     */
    private static String digits(long val) {
        return Long.toHexString(HI | (val & BINARY_32)).substring(1);
    }

    /**
     * 生成32位UUID
     * <p>
     * 如果仅用于生成32为UUID，更优的函数是{@link #genUUID32()}
     * <p>
     * 此函数用于{@link UUID#toString()}结果的转换。
     * <p>
     * old version:
     * uuid.substring(0, 8) + uuid.substring(9, 13) + uuid.substring(14, 18) + uuid.substring(19, 23) + uuid.substring(24)
     * <p>
     * 执行效率与历史版本性基本一致，新版代码减少了创建对象的次数。
     *
     * @return uuid
     */
    public static String toUUID32(String uuid) {
        StringBuilder sb = new StringBuilder(32);
        char ch;
        for (int i = 0; i < uuid.length(); i++) {
            ch = uuid.charAt(i);
            if (ch != '-') {
                sb.append(ch);
            }
        }
        return sb.toString();
    }

    /**
     * 生成 22 位 UUID
     * <p>
     * base64 算法：8 位二进制数，转为 1 个字节，128 位正好 16 字节，
     * 16 字节产生 24位 base64 字符串，去除占位 2 个字母，正好 22 位。
     *
     * @return uuid
     */
    public static String base64UUID() {
        UUID uuid = UUID.randomUUID();
        long msb = uuid.getMostSignificantBits();
        long lsb = uuid.getLeastSignificantBits();
        int x;
        byte[] buffer = new byte[16];
        for (int i = 0; i < 8; i++) {
            x = 8 * (7 - i);
            buffer[i] = (byte) (int) (msb >> x);
            buffer[i + 8] = (byte) (int) (lsb >> x);
        }
        return Base64.encodeString(buffer).substring(0, 22);
    }

    /**
     * 生成 22 位 UUID
     * <p>
     * UUID 128位，每 6 位用一个 64 进制字符表示，正好 22 位。
     * 此函数采用的是 64 进制转换，而不是 Base64 编码，因为 128 不能被 6 整除，第 11 位和第 22 位，只能是字母。
     *
     * @return uuid
     */
    public static String genUUID22() {
        UUID uuid = UUID.randomUUID();
        long msb = uuid.getMostSignificantBits();
        long lsb = uuid.getLeastSignificantBits();
        int x;
        char[] buffer = new char[22];
        for (int i = 0; i < 11; i++) {
            x = 6 * (10 - i);
            buffer[i] = digits64((int) (msb >> x));
            buffer[i + 11] = digits64((int) (lsb >> x));
        }
        return new String(buffer);
    }

    /**
     * Returns val represented by the specified number of 64 digits.
     */
    private static char digits64(int val) {
        return CHARS[val & 63];
    }

    /**
     * 生成 8 位 UUID 标识码
     * <p>
     * 作为唯一 ID 使用的时候，很明显，长度不够，必定会出现重复，
     * 但是可以作为 uuid 的标识码使用，类似于数字签名，如果 uuid 被篡改，生成的标识码会发生变化。
     *
     * @param uid 32 位 uuid
     * @return uuid 标识码
     */
    public static String toUUID8(String uid) {
        StringBuilder shortBuffer = new StringBuilder();
        for (int i = 0; i < 8; i++) {
            String str = uid.substring(i * 4, i * 4 + 4);
            int x = Integer.parseInt(str, 16);
            shortBuffer.append(CHARS[x & 61]);
        }
        return shortBuffer.toString();
    }

    private final static char[] CHARS = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
            'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2',
            '3', '4', '5', '6', '7', '8', '9', '+', '/'};
}
